package yartiss.service.viewmodel;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.table.TableModel;

import yartiss.engine.simulation.IEnergyProfile;
import yartiss.engine.simulation.ISchedulingPolicy;
import yartiss.engine.simulation.Simulation;
import yartiss.engine.simulation.SimulationParameters;
import yartiss.engine.simulation.energy.profiles.EnergyProfileFactory;
import yartiss.engine.simulation.events.DeadlineMissedEvent;
import yartiss.engine.simulation.events.EnergyFailureEvent;
import yartiss.engine.simulation.events.EventVisitorAdapter;
import yartiss.engine.simulation.io.TasksetReader;
import yartiss.engine.simulation.scheduling.policies.SchedulingPolicyFactory;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.ITaskSet;
import yartiss.framework.concurrency.Consumer;
import yartiss.framework.concurrency.IElementConsumedListener;
import yartiss.framework.concurrency.Producer;
import yartiss.framework.concurrency.QueueEntry;
import yartiss.framework.view.AbstractView;
import yartiss.framework.view.IView;
import yartiss.framework.viewmodel.AbstractViewModel;
import yartiss.service.model.IChartModel;
import yartiss.service.model.Metric;
import yartiss.service.model.MetricsChartModel;
import yartiss.service.model.PoliciesTableModel;
import yartiss.service.model.Scenario;
import yartiss.service.model.ScenariosChartModel;
import yartiss.service.model.ScenariosTableModel;
import yartiss.service.model.TasksSetsTableModel;

public class BenchmarkViewModel extends AbstractViewModel{

	private AbstractView view;
	private int duration = 1000;
	private boolean isContentChanged;
	private IChartModel<Scenario> scenariosModel = new ScenariosChartModel();
	private IChartModel<Metric> metricsModel 	 = new MetricsChartModel();
	private final PoliciesTableModel policiesModel = new PoliciesTableModel();
	private final ScenariosTableModel scenariosTableModel = new ScenariosTableModel();
	private final BlockingQueue<QueueEntry<Simulation>> queue = new LinkedBlockingQueue<QueueEntry<Simulation>>(10000);
	private Consumer<Simulation> consumer;
	private Producer<Simulation> producer;
	private boolean debug = false;
	private String systemsFile = "1000_systems_pr_10";
	private int nbProc = 1;
	private final static String [] metrcisNames = new String[]{"Failure","sim duration","Tmax","Tmin","BP","IP"};
	private final BoundedRangeModel globalProgressModel = new DefaultBoundedRangeModel(0, 0, 0, 100000000);

	
	public BenchmarkViewModel(){
		policiesModel.addPolicy("EH_PFP_1", "1");
		policiesModel.addPolicy("EH_PFP_4", "");
		
		scenariosTableModel.addScenario(10,200,0,0,true,"constant");
		scenariosTableModel.addScenario(15,200,0,0,true,"constant");
		scenariosTableModel.addScenario(20,200,0,0,true,"constant");
		scenariosTableModel.addScenario(25,200,0,0,true,"constant");
		scenariosTableModel.addScenario(30,200,0,0,true,"constant");
		scenariosTableModel.addScenario(35,200,0,0,true,"constant");
	}

	@Override
	public IView getView() {
		return view;
	}

	public void setView(AbstractView view) {
		unregisterViewListeners();
		this.view = view;
		view.setDataContext(this);
		registerViewListeners();		
	}
	
	
	
	private PropertyChangeListener listener = new PropertyChangeListener() {
		@Override	public void propertyChange(PropertyChangeEvent evt) {
			Object value = view.getProperty(evt.getPropertyName());
			setProperty(evt.getPropertyName(), value);
			setIsContentChanged(true);
		}
	};
	private void unregisterViewListeners(){
		if(view != null)
			view.unregisterPropertyChangeListener(listener);
	}
	
	private void registerViewListeners(){
		if(view != null)
			view.registerPropertyChangeListerner(listener);
	}

	/**
	 * @return the scenariosModel
	 */
	public IChartModel<Scenario> getScenariosModel() {
		return scenariosModel;
	}

	/**
	 * @param scenariosModel the scenariosModel to set
	 */
	public void setScenariosModel(IChartModel<Scenario> scenariosModel) {
		this.scenariosModel = scenariosModel;
		firePropertyChanged("ScenariosModel", scenariosModel, scenariosModel);
	}

	/**
	 * @return the metricsModel
	 */
	public IChartModel<Metric> getMetricsModel() {
		return metricsModel;
	}

	/**
	 * @return the policiesModel
	 */
	public TableModel getPoliciesModel() {
		return policiesModel;
	}


	/**
	 * @return the globalProgressModel
	 */
	public BoundedRangeModel getGlobalProgressModel() {
		return globalProgressModel;
	}

	/**
	 * @return the scenariosTableModel
	 */
	public ScenariosTableModel getScenariosTableModel() {
		return scenariosTableModel;
	}

	/**
	 * @return the isContentChanged
	 */
	public boolean getIsContentChanged() {
		return isContentChanged;
	}

	public void setIsContentChanged(boolean isContentChanged) {
		this.isContentChanged = isContentChanged;
		firePropertyChanged("IsContentChanged", isContentChanged, isContentChanged);
	}
	
	/**
	 * @return the debug
	 */
	public boolean getDebug() {
		return debug;
	}

	/**
	 * @param debug the debug to set
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
		firePropertyChanged("Debug", debug, debug);
	}
	
	/**
	 * @return the duration
	 */
	public int getDuration() {
		return duration;
	}

	/**
	 * @param duration the duration to set
	 */
	public void setDuration(int duration) {
		this.duration = duration;
		firePropertyChanged("Duration", duration, duration);
	}
	
	/**
	 * @return the nbProc
	 */
	public int getNbProc() {
		return nbProc;
	}

	/**
	 * @param nbProc the nbProc to set
	 */
	public void setNbProc(int nbProc) {
		this.nbProc = nbProc;
		firePropertyChanged("NbProc", nbProc, nbProc);
	}
	
	/**
	 * @return the systemsFile
	 */
	public String getSystemsFile() {
		return systemsFile;
	}

	/**
	 * @param systemsFile the systemsFile to set
	 */
	public void setSystemsFile(String systemsFile) {
		this.systemsFile = systemsFile;
		firePropertyChanged("SystemsFile", systemsFile, systemsFile);
	}
	
	/**
	 * @return the startSimulationCommand
	 */
	public ActionListener getStartSimulationCommand() {
		return startSimulationCommand;
	}
	
	private void updateDebugResults(Simulation sim, Scenario senario){
		TasksSetsTableModel model = senario.getSetsModel();
		EventsVisitor visitor = new EventsVisitor();
		sim.getStatistics().getLastEvent().accept(visitor);
		model.updateEntry(sim.getSimulationParameters().getTaskSet().getId(), sim.getSimulationParameters().getSchedulingPolicy().getPolicyName(),sim.getSimulationParameters().getTaskSet(),senario.getProfile(),duration,  visitor.result);
	}
	
	class EventsVisitor extends EventVisitorAdapter{
		public yartiss.service.model.TasksSetsTableModel.Result result = yartiss.service.model.TasksSetsTableModel.Result.SUCCESS;
		@Override
		public void visit(EnergyFailureEvent e) {
			result = yartiss.service.model.TasksSetsTableModel.Result.ENERGY_FAILURE;
		}

		@Override
		public void visit(DeadlineMissedEvent e) {
			result = yartiss.service.model.TasksSetsTableModel.Result.DEADLINE_MISSED;
		}
	}
	
	private final ActionListener startSimulationCommand = new ActionListener() {
		@Override	public void actionPerformed(ActionEvent e) {
			System.out.println("start command");
			setIsContentChanged(false);
			scenariosModel.clear();
			for (Scenario  scenario : scenariosTableModel.getAll(duration)) {
				scenariosModel.add(scenario, scenario.getScenarioName() );
			}
			
			metricsModel.clear();
			for (String metric : metrcisNames) {
				metricsModel.add(new Metric(metric), metric);
			}
			
			globalProgressModel.setValue(0);
		
			new Thread(new Runnable() {
			      public void run() {
			        SwingUtilities.invokeLater(new Runnable() {
			          public void run() {
			        	  SwingWorker<Void, String> task = new SwingWorker<Void, String>() {
			      			@Override	protected Void doInBackground() throws Exception {
			      				producer = new Producer<Simulation>(queue);
			      				consumer = new Consumer<Simulation>(queue, new IElementConsumedListener<Simulation>() {
			      					@Override
			      					public void consume(Simulation sim) {
			      						String scenarioName = sim.getSimulationParameters().getEnergyProfile().getMaxEnergyLevel()+"_"+sim.getSimulationParameters().getEnergyProfile().getPowerRate();
			      						Scenario scenario = scenariosModel.getElementByName(scenarioName);
			      						scenario.updatePolicyStats(sim.getSimulationParameters().getSchedulingPolicy().getPolicyName(), sim.getStatistics());

			      						if(debug)
			      							updateDebugResults(sim, scenario);
			      						for (String metric : metrcisNames) {
			      							Metric m = metricsModel.getElementByName(metric);
			      							m.updatePolicyStats(scenario, sim.getSimulationParameters().getSchedulingPolicy().getPolicyName(), sim.getStatistics());
			      						}
			      						globalProgressModel.setValue(globalProgressModel.getValue()+1);
			      					}
			      				});

			      				consumer.start();

			      				for (Scenario s : scenariosTableModel.getAll(duration)) {
			      					IEnergyProfile ep  = s.getProfile();
			      					Scenario scenario = scenariosModel.getElementByName(ep.getMaxEnergyLevel()+ "_" + ep.getPowerRate());
			      					System.out.println("start ... producing");
			      					for (ISchedulingPolicy policy : policiesModel.getAll(duration)) {
			      						System.out.println("systems file ="+ systemsFile);
			      						TasksetReader reader = new TasksetReader(systemsFile);
			      						int nbEntries = 0;
			      						System.out.println("-----------" + policy.getPolicyName()+ " :  started");
			      						ITask[] tasks = null;
			      						while((tasks = reader.readTaskSet()) != null){
			      							nbEntries ++;
			      							final List<Integer> paramss = policy.getParameters()==null ? null : new LinkedList<Integer>(policy.getParameters());
			      							final ISchedulingPolicy policy2 = SchedulingPolicyFactory.getInstance(policy.getPolicyName());
			      							final ITask[] tasks2 = tasks;
			      							final int id = nbEntries;
			      							policy2.setParameters(paramss);
			      							final IEnergyProfile fep = EnergyProfileFactory.getInstance("defaultEnergyProfile", (int)ep.getPowerRate(), (int)ep.getMaxEnergyLevel(), (int)ep.getMinEnergyLevel(), (int)ep.getStartEnergyLevel());
			      							producer.produce(new Callable<Simulation>() {
			      								@Override
			      								public Simulation call() throws Exception {
			      									Simulation sim = new Simulation(buildSimulationParameters(fep, policy2, tasks2, id, getNbProc()));
			      									sim.simulate();
			      									return sim;
			      								}
			      							});
			      						}
			      						reader.close();
			      						scenario.setSystemsCount(nbEntries) ;
			      						globalProgressModel.setMaximum(nbEntries*policiesModel.getRowCount()*scenariosTableModel.getRowCount());
			      					}
			      				}
			      				System.out.println("finish producing");
			      				return null;
			      			}
			      		};
			      		task.execute();
			          }
			        });
			      }
			  }).start();
		}
	};
	
	
	private SimulationParameters buildSimulationParameters(IEnergyProfile energyProfile, ISchedulingPolicy policy, ITask[] tasks, int id, int nbProc) {
		ITaskSet taskset = policy.createTaskSet();
		taskset.setId(id);
		for (ITask task : tasks) {
			taskset.addTask(task.cloneTask());
		}
		return new SimulationParameters(policy, duration ,	energyProfile, taskset,nbProc, true);
	}

	

	


	

}
